package org.xtremeobjects.domain.model.java5;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.xtremeobjects.anotations.Label;
import org.xtremeobjects.anotations.ddd.Repository;
import org.xtremeobjects.domain.model.Association;
import org.xtremeobjects.domain.model.Clazz;
import org.xtremeobjects.domain.model.ClazzRepository;
import org.xtremeobjects.domain.model.Operation;
import org.xtremeobjects.domain.model.Property;
import org.xtremeobjects.domain.model.expression.ExpressionEvaluator;

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;


public class ClazzJava5 implements Clazz {

	
	public Class<?> javaClass;
	private List<PropertyJava5> properties = new ArrayList<PropertyJava5>();
	private List<AssociationJava5> associations = new ArrayList<AssociationJava5>();
	private List<OperationJava5> operations = new ArrayList<OperationJava5>();
	private ClazzRepository clazzRepository;
	private ExpressionEvaluator expressionEvaluator;

	private ExpressionEvaluator getExpressionEvaluator() {
		return expressionEvaluator;
	}
	
	@Inject
	public ClazzJava5(@Assisted Class<?> javaClass, ClazzRepository clazzRepository, ExpressionEvaluator expressionEvaluator) {
		this.clazzRepository = clazzRepository;
		this.expressionEvaluator = expressionEvaluator;
		this.javaClass = javaClass;
		createProperties(javaClass);
		createAssociations(javaClass);
		createOperations(javaClass);
	}
	
	@Inject
	public ClazzJava5(@Assisted String javaClass, ClazzRepository clazzRepository, ExpressionEvaluator expressionEvaluator ) throws ClassNotFoundException {
		this(Class.forName(javaClass), clazzRepository, expressionEvaluator);
	}


	public String getName() {
		return javaClass.getCanonicalName();
	}

	/* (non-Javadoc)
	 * @see org.xtremeobjects.domain.model.IClazz#getPropertyNames()
	 */
	public String[] getPropertiesNames() {
			ArrayList<String> properties = new ArrayList<String>();

			for (Property property : getProperties()) {
				properties.add(property.getName());
			}

			return properties.toArray(new String[properties.size()]);
		}

	/* (non-Javadoc)
	 * @see org.xtremeobjects.domain.model.IClazz#getProperties()
	 */
	public List<Property> getProperties() {
		return Collections.unmodifiableList(new ArrayList<Property>(properties));
	}


	/* (non-Javadoc)
	 * @see org.xtremeobjects.domain.model.IClazz#getAssociations()
	 */
	public List<Association> getAssociations() {
		return Collections.unmodifiableList(new ArrayList<Association>(associations));
	}


	private void createProperties(Class<?> javaClass) {
		AbstractMethodSpecification propertyGetterSpecification = new PropertyGetterSpecification();
		for (java.lang.reflect.Method method : javaClass.getMethods()) {

			if(propertyGetterSpecification.isSatisfiedBy(method)) {
				String propertyName = getPropertyName(method);
				Method getter = method;
				String setterName = "s" + method.getName().substring(1);
				Class<?> parameters[] = new Class[1];
				parameters[0]=getter.getReturnType();
				Method setter=null;
				try {
				setter = javaClass.getMethod(setterName, parameters );
				} catch ( NoSuchMethodException ex ) {
					
				}
				properties.add(new PropertyJava5(this, propertyName, getter, setter, clazzRepository));
			}
		}
	}

	private String getPropertyName(Method method) {
		String propertyName = method.getName();
		propertyName = propertyName.substring(3);
		propertyName = Character.toLowerCase(propertyName.charAt(0))
				+ propertyName.substring(1);
		return propertyName;
	}

	private void createAssociations(Class<?> javaClass2) {
		AbstractMethodSpecification propertyGetterSpecification = new PropertyGetterSpecification(true);
		for (java.lang.reflect.Method method : javaClass.getMethods()) {
			if(propertyGetterSpecification.isSatisfiedBy(method)) {
				String propertyName = getPropertyName(method);
				Method getter = method;
				Method setter = null;
				associations.add(new AssociationJava5(this, propertyName, getter, setter, clazzRepository));
			}
		}
	}

	private void createOperations(Class<?> javaClass2) {
		AbstractMethodSpecification specification = new OperationSpecification();
		for (java.lang.reflect.Method method : javaClass.getMethods()) {
			if(specification.isSatisfiedBy(method)) {
				operations.add(new OperationJava5(this, method));
			}
		}
	}

	public Class<?> getImplementationClass() {
		return this.javaClass;
	}

	public String[] getAssociationsNames() {
		ArrayList<String> associations = new ArrayList<String>();

		for (Association association : getAssociations()) {
			associations.add(association.getName());
		}

		return associations.toArray(new String[associations.size()]);
	}

	public String getLabel(Object obj) {
		String label = getLabelFromAnnotation(obj);
		if(label==null)
			label = obj.toString();
		return label;
	}

	private String getLabelFromAnnotation(Object obj) {
		String label = null;
		Label labelAnnotation = (Label)javaClass.getAnnotation(Label.class);
		if(labelAnnotation!=null && !labelAnnotation.value().isEmpty()) {
			label = labelAnnotation.value();
			if(label.contains("${") || label.contains("#{") ) {
				try {
					label = getExpressionEvaluator().evaluate(obj, label).toString();
				} catch (Throwable e) {
					System.out.println("Expression evaluator EL not working or couldn�t evaluate �"+label+"� on " + obj.toString());
					label = null;
				} finally {
				}
			}
			
		}
		/**
		if(labelAnnotation!=null && !labelAnnotation.property().isEmpty()) {
			Property property = getPropertyByName(labelAnnotation.property());
			if(property!=null)
				label = property.getValue(obj).toString();
			else
				;// TODO Log property not found error
		}
		/**/
		return label;
	}
	
	



	public Property getPropertyByName(String propertyName) {
		for (Property property : getProperties()) {
			if(property.getName().equals(propertyName))
				return property;
		}
		return null;
	}

	public String getDescription(Object obj) {
		return getLabel(obj) + " Information...";
	}

	public String getId(Object obj) {
		Property id = getPropertyByName("id");
		if (id!=null) {
			return id.getValue(obj).toString();
		}
		return null;
	}


	public List<Operation> getOperations() {
		return Collections.unmodifiableList(new ArrayList<Operation>(operations));
	}

	public boolean isRepository() {
		return getImplementationClass().isAnnotationPresent(Repository.class);
	}


	public List<Operation> getPublicOperations() {
		ArrayList<Operation> publicOperations = new ArrayList<Operation>();
		for (Operation operation : operations) {
			String name = operation.getName();
			if(name.startsWith("get") /*|| name.startsWith("add") || name.startsWith("remove") /**/ || name.startsWith("set"))
				continue;
			publicOperations.add(operation);
		}
		return Collections.unmodifiableList(publicOperations);
	}


	public Repository getRepositoryAnnotation() {
		return (Repository)getImplementationClass().getAnnotation(Repository.class);
	}


	public Clazz getSuperclazz() {
		Class<?> superclass = this.getImplementationClass().getSuperclass();
		if(superclass==null)
			return null;/**/
		return clazzRepository.findOrCreateByClass(superclass);
	}


	public String getSimpleName() {
		return this.getImplementationClass().getSimpleName();
	}

	@Override
	public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
		return getAnnotation(annotationClass);
	}

	@Override
	public boolean isAnnotationPresent(
			Class<? extends Annotation> annotationClass) {
		return isAnnotationPresent(annotationClass);
	}

}
